#pragma once
#include "SLList.h"

namespace Renderer
{

	struct AABB
	{
		D3DXVECTOR3 minPoint;
		D3DXVECTOR3 maxPoint;

		AABB() : minPoint(INFINITY, INFINITY, INFINITY), maxPoint(-INFINITY, -INFINITY, -INFINITY)
		{
		}

		D3DXVECTOR3 center()
		{
			return 0.5f * (minPoint + maxPoint);
		}
	};

	struct BoundingSphere
	{
		D3DXVECTOR3 pos;
		float radius;

		BoundingSphere() : pos(0.0f, 0.0f, 0.0f), radius(0.0f) {}
	};

	struct sMesh
	{
		char* name;
		ID3DXMesh* mesh;
		ID3DXMesh* skinMesh;
		ID3DXSkinInfo* skinInfo;

		DWORD numMaterials;
		D3DMATERIAL9* materials;
		IDirect3DTexture9** textures;

		DWORD numBones;
		D3DXMATRIX* boneMatrices;
		D3DXMATRIX** frameMatrices;

		AABB	boundingBox;
		BoundingSphere boundingSphere;

		sMesh	*next;

		sMesh() : name(NULL), 
			mesh(NULL), 
			skinMesh(NULL), 
			skinInfo(NULL), 
			numMaterials(0),
			materials(NULL),
			textures(NULL),
			numBones(0),
			boneMatrices(NULL),
			frameMatrices(NULL),
			next(NULL)
		{}

		~sMesh()
		{
			delete [] name;
			ReleaseCOM(mesh);
			ReleaseCOM(skinMesh);
			ReleaseCOM(skinInfo);
			delete [] materials;
			if(textures != NULL)
			{
				for(DWORD i = 0;i < numMaterials; ++i)
					ReleaseCOM(textures[i]);
				delete [] textures;
			}
			delete [] boneMatrices;
			delete [] frameMatrices;
			delete next;
		}

		sMesh* FindMesh(char * name)
		{
			sMesh * mesh;

			if(name == NULL)
				return this;

			if(name != NULL && strcmp(name, this->name) == 0)
				return this;

			if(next != NULL)
			{
				mesh = next->FindMesh(name);
				if(mesh != NULL)
					return mesh;
			}

			return NULL;
		}

		DWORD GetNumMaterials()
		{
			return numMaterials;
		}

		D3DMATERIAL9 * GetMaterial(ULONG num)
		{
			if(num > numMaterials || materials == NULL)
				return NULL;

			return &materials[num];
		}

		IDirect3DTexture9 * GetTexture(ULONG num)
		{
			if(num >= numMaterials || textures == NULL)
				return NULL;

			return textures[num];
		}

		void CopyFrameToBoneMatrices()
		{
			if(numBones != 0 && boneMatrices != NULL && frameMatrices != NULL)
			{
				for(DWORD i = 0; i < numBones; ++i)
				{
					if(frameMatrices[i] != NULL)
					{
						D3DXMatrixMultiply(&boneMatrices[i], skinInfo->GetBoneOffsetMatrix(i), frameMatrices[i]);
					}
					else
						D3DXMatrixIdentity(&boneMatrices[i]);
				}
			}

			if(next != NULL)
				next->CopyFrameToBoneMatrices();
		}
	};

	struct sFrame
	{
		char * name;
		SLList<sMesh *> meshList;

		D3DXMATRIX combinedMatrix;
		D3DXMATRIX transformedMatrix;
		D3DXMATRIX originalMatrix;

		sFrame * parent;
		sFrame * child;
		sFrame * sibling;

		sFrame() : name(NULL), parent(NULL), child(NULL), sibling(NULL)
		{
			D3DXMatrixIdentity(&combinedMatrix);
			D3DXMatrixIdentity(&transformedMatrix);
			D3DXMatrixIdentity(&originalMatrix);
		}

		~sFrame()
		{
			delete [] name;
			delete child;
			delete sibling;
		}

		sFrame * FindFrame(char * name)
		{
			sFrame * frame;

			if(name == NULL)
				return this;

			if(name != NULL && strcmp(name, this->name) == 0)
				return this;

			if(child != NULL)
			{
				frame = child->FindFrame(name);
				if(frame != NULL)
					return frame;
			}

			if(sibling != NULL)
			{
				frame = sibling->FindFrame(name);
				if(frame != NULL)
					return frame;
			}

			return NULL;
		}

		void ResetMatrices()
		{
			transformedMatrix = originalMatrix;

			if(child != NULL)
				child->ResetMatrices();

			if(sibling != NULL)
				sibling->ResetMatrices();
		}

		void AddMesh(sMesh * mesh)
		{
			meshList.addHead(mesh);
		}

	};

}